﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

//Import TileLib to load maps
using TiledLib;


namespace WindowsGame
{
    /// <summary>
    /// This class uses the TiledLib library to load maps from .tmx-files.
    /// Using a well written library is more clean than to write this component from scratch.
    /// </summary>
    public class MapComponent : Microsoft.Xna.Framework.GameComponent
    {
        //
        // Maps will be divided into worlds and each world will contain a set number of maps.
        // Map variables will be named map, then world number, then an underscore and lastly the map number
        // World 1 map 2 will therefore be "map1_2"
        //


        //Debug variables
        Texture2D testGraphic;
        SpriteFont segoeUI;
        public bool drawBounds;

        //
        //"Real" variables
        //

        //Map variables
        Map currentMap;
        Map map1_1;
        Map map1_2;

        //Use dictionary to connect String type to Map type, so TiledLib only needs to be imported in this class
        Dictionary<String, Map> mapDictionary;

        //Enumerator for different bound types
        public enum BoundType
        {
            Solid,
            Passable,
            Hurt,
            Slow,
            Goal,
            Collectible,
            Spawn,
            Unknown
        }

        //Bounds will consist of two variables. One that tells where the bound is and one that tells what type it is.
        public struct Bounds
        {
            public Rectangle rect;
            public BoundType type;
        }
        //Make a list for putting these bounds in
        List<Bounds> boundList;

        //String that holds information on which level to load next
        String goalString;

        //Bool for determining whether a new map is loading or not
        bool loadingMap;

        Camera2DComponent camera;

        MapComponents.CloudSpawnerComponent cloudSpawner;
        PlayerComponent player; //Need player to make him spawn
        CollectibleComponents.CollectibleSpawnerComponent collectibleSpawner;

        public MapComponent(Game game, Camera2DComponent camera, PlayerComponent player)
            : base(game)
        {
            this.camera = camera;
            this.player = player;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            mapDictionary = new Dictionary<string, Map>();
            boundList = new List<Bounds>();

            cloudSpawner = new MapComponents.CloudSpawnerComponent(this.Game, camera);
            cloudSpawner.Initialize();

            base.Initialize();
        }

        /// <summary>
        /// Let component load relevant files
        /// </summary>
        /// <param name="Content">Use manager to load files</param>
        public void LoadContent(ContentManager Content, CollectibleComponents.CollectibleSpawnerComponent spawner)
        {
            //Load maps
            map1_1 = Content.Load<Map>("Levels\\map1_1");
            map1_2 = Content.Load<Map>("Levels\\map1_2");

            //Add maps to dictionary
            mapDictionary.Add("map1_1", map1_1);
            mapDictionary.Add("map1_2", map1_2);

            //Load cloud spawner
            cloudSpawner.LoadContent(Content);

            //Inherit in load to ensure that parent has initialized class properly
            this.collectibleSpawner = spawner;

            //Debugging
            testGraphic = Content.Load<Texture2D>("GFX\\TestGraphic");
            segoeUI = Content.Load<SpriteFont>("Fonts\\SegoeUI_10");
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            //Update cloud spawner
            cloudSpawner.Update(gameTime);

            base.Update(gameTime);
        }
            

        /// <summary>
        /// Let the component draw corresponding graphics itself.
        /// </summary>
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Camera2DComponent camera)
        {
            //Begin spritebatch
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, camera.getMatrix());

            //Draw background
            cloudSpawner.Draw(spriteBatch);

            //Use TiledLib function to draw map.
            //Draw map
            //Start by enabling all layer in "background"
            for (int i = 0; i < currentMap.Layers.Count; i++)
                currentMap.Layers[i].Visible = true;
            //And disable foreground layers
            try { currentMap.GetLayer("Foreground").Visible = false; }
            catch { }
            //Then draw the map
            currentMap.Draw(spriteBatch);

            #region debugging code
            //Temp-var

            if (drawBounds)
            {
                int fontY = 15;
                for (int i = 0; i < this.getBounds().Count; i++)
                {
                    //Get rectangle list bounds using the getter in this class to demonstrate how other parts of the program would get the
                    //bounding boxes for the map objects
                    switch (this.getBounds()[i].type)
                    {
                        case BoundType.Solid:
                            spriteBatch.Draw(testGraphic, this.getBounds()[i].rect, Color.Multiply(Color.Black, 0.2f));
                            spriteBatch.DrawString(segoeUI, "Solid", new Vector2(this.getBounds()[i].rect.X, this.getBounds()[i].rect.Y - fontY), Color.Black);
                            break;
                        case BoundType.Passable:
                            spriteBatch.Draw(testGraphic, this.getBounds()[i].rect, Color.Multiply(Color.Green, 0.2f));
                            spriteBatch.DrawString(segoeUI, "Passable", new Vector2(this.getBounds()[i].rect.X, this.getBounds()[i].rect.Y - fontY), Color.Black);
                            break;
                        case BoundType.Hurt:
                            spriteBatch.Draw(testGraphic, this.getBounds()[i].rect, Color.Multiply(Color.Red, 0.2f));
                            spriteBatch.DrawString(segoeUI, "Hurt", new Vector2(this.getBounds()[i].rect.X, this.getBounds()[i].rect.Y - fontY), Color.Black);
                            break;
                        case BoundType.Slow:
                            spriteBatch.Draw(testGraphic, this.getBounds()[i].rect, Color.Multiply(Color.Yellow, 0.2f));
                            spriteBatch.DrawString(segoeUI, "Slow", new Vector2(this.getBounds()[i].rect.X, this.getBounds()[i].rect.Y - fontY), Color.Black);
                            break;
                        case BoundType.Goal:
                            spriteBatch.Draw(testGraphic, this.getBounds()[i].rect, Color.Multiply(Color.Blue, 0.2f));
                            spriteBatch.DrawString(segoeUI, "Goal", new Vector2(this.getBounds()[i].rect.X, this.getBounds()[i].rect.Y - fontY), Color.Black);
                            break;
                        case BoundType.Collectible:
                            spriteBatch.Draw(testGraphic, this.getBounds()[i].rect, Color.Multiply(Color.LightBlue, 0.2f));
                            spriteBatch.DrawString(segoeUI, "Collectible", new Vector2(this.getBounds()[i].rect.X, this.getBounds()[i].rect.Y - fontY), Color.Black);
                            break;
                        case BoundType.Spawn:
                            spriteBatch.Draw(testGraphic, this.getBounds()[i].rect, Color.Multiply(Color.LemonChiffon, 0.2f));
                            spriteBatch.DrawString(segoeUI, "Spawn", new Vector2(this.getBounds()[i].rect.X, this.getBounds()[i].rect.Y - fontY), Color.Black);
                            break;
                        case BoundType.Unknown:
                            spriteBatch.Draw(testGraphic, this.getBounds()[i].rect, Color.Multiply(Color.Red, 0.2f));
                            spriteBatch.DrawString(segoeUI, "Uknown", new Vector2(this.getBounds()[i].rect.X, this.getBounds()[i].rect.Y - fontY), Color.Black);
                            break;
                        default:
                            break;
                    }

                }
            }
            #endregion

            //End spritebatch
            spriteBatch.End();
        }

        /// <summary>
        /// Separate draw to be able to draw on different points in time. Use this for foreground layers, for example
        /// </summary>
        public void LateDraw(GameTime gameTime, SpriteBatch spriteBatch, Camera2DComponent camera)
        {
            //Begin spritebatch
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, camera.getMatrix());

            //Draw map
            //Start by disabling all layer in "background"
            for (int i = 0; i < currentMap.Layers.Count; i++)
                currentMap.Layers[i].Visible = false;
            //And enable foreground layers
            try { currentMap.GetLayer("Foreground").Visible = true; }
            catch { }
            //Then draw the map
            currentMap.Draw(spriteBatch);

            spriteBatch.End();
        }

        //Getter for getting map dimension. Multiply by tile size to get right measurements
        //Map.Width only returns number of tiles, thus showing the wrong dimensions.
        public Vector2 getMapDimensions() { return new Vector2(currentMap.Width * currentMap.TileWidth, currentMap.Height * currentMap.TileHeight); }


        //Function for loading map and putting bounds into list
        public void changeMap(String mapName)
        {
            //Use bool to determine whether map is loading or not
            loadingMap = true;

            //Check if map is present in dictionary
            if (mapDictionary.ContainsKey(mapName))
                currentMap = mapDictionary[mapName];
            else
                currentMap = map1_1; //TODO: Add error handling here


            MapObjectLayer boundLayer = currentMap.GetLayer("Bounds") as MapObjectLayer; //Gets layer that holds bounds.
            boundList = new List<Bounds>(); //Reset bound list
            
            //Add bounds to rectangle list
            for (int i = 0; i < boundLayer.Objects.Count; i++)
            {
                Bounds bounds = new Bounds(); //Creates a temporary bound that is later on going to be put into list
                bounds.rect = boundLayer.Objects[i].Bounds; //Put rectangle into list

                //Use enumerator to set type to avoid confusion with strings
                switch (boundLayer.Objects[i].Type)
                {
                    case "Solid":
                        bounds.type = BoundType.Solid;
                        break;
                    case "Passable":
                        bounds.type = BoundType.Passable;
                        break;
                    case "Hurt":
                        bounds.type = BoundType.Hurt;
                        break;
                    case "Slow":
                        bounds.type = BoundType.Slow;
                        break;
                    case "Goal":
                        bounds.type = BoundType.Goal;
                        goalString = boundLayer.Objects[i].Properties["mapName"].RawValue; //Get the map name that the object goal holds
                        break;
                    case "Collectible":
                        //Spawn collectible using this
                        bounds.type = BoundType.Collectible;
                        break;
                    case "Spawn":
                        //Spawn player here
                        bounds.type = BoundType.Spawn;
                        player.Spawn(new Vector2(bounds.rect.X, bounds.rect.Y));
                        break;
                    default:
                        bounds.type = BoundType.Unknown;
                        break;
                }

                //Finally add bound to list
                boundList.Add(bounds);
            }


            collectibleSpawner.SpawnFromBounds();

            //Set bool to false to show that map has finished loading
            loadingMap = false;
        }

        //Getter for bound list
        public List<Bounds> getBounds() { return boundList; }

        //Find out whether map is loading or not. Getter for bool "loadingMap"
        public bool isLoading() { return loadingMap; }

        public string GetGoalString() { return goalString; }

        //Fun easter egg stuff
        //Repaints every tile in random colors
        public void discoMode(GameTime gameTime)
        {
            Random rnd = new Random();
            for (int i = 0; i < currentMap.Tiles.Count; i++)
            {
                if (currentMap.Tiles[i] != null)
                    currentMap.Tiles[i].Color = new Color(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255));
            }
        }
   }
}
